classdef uiStruct2Tree < handle
    properties
        rootData
        container
        tree
        ui
        
        selectedNodePath
    end
    
    events
        SelectedNodeChanged
    end
    
    methods
        function obj = uiStruct2Tree(s, varargin)
            p = inputParser;
            p.addRequired('s', @(x) isscalar(x) && isstruct(x));
            p.addParameter('container', [], @(x) isempty(x) || isa(x, 'matlab.ui.Figure')|| isa(x, 'matlab.ui.container.Panel'));
            p.parse(s, varargin{:});
            
            obj.rootData = s;
            
            if isempty(p.Results.container)
                obj.container = uifigure();
            else
                obj.container = p.Results.container;
            end
            
            obj.make_UIs();
            obj.buildTree(s, 'Root', 'obj.rootData');
            obj.ui.buttonHome.Enable = false;
        end
        
        function res = getSelectedData(obj)
            res = eval(obj.selectedNodePath);
        end
        %%
        function buildTree(obj, s, rootTextStr, rootPathStr)
            nodeData.class = class(s);
            nodeData.text = rootTextStr;
            nodeData.path = rootPathStr;
            
            delete(obj.tree.Children);            
            rootnode = uitreenode(obj.tree, 'Text', rootTextStr, 'NodeData', nodeData);
            cellfun(@(name) obj.buildNode(rootnode, s, name), fieldnames(s));
        end

        function buildNode(obj, parentNode, s, name)
            if ~isscalar(s)
                arrayfun(@(x) obj.buildNode(parentNode, x, name), s);
                return;
            end

            val = s.(name);
            
            switch class(val)
                case 'char'
                    nodeNameStr = sprintf('%s [%s x %d] = "%s"', name, class(val), length(val), val);
                case 'datetime'
                    nodeNameStr = sprintf('%s [%s x %d] = "%s"', name, class(val), length(val), datestr(val));
                otherwise
                    nodeNameStr = sprintf('%s [%s x %d]', name, class(val), length(val));
            end

            if iscell(val)
                nodeData.class = class(val);
                nodeData.text = nodeNameStr;
                nodeData.path = sprintf('%s.%s', parentNode.NodeData.path, name);
                childNode = uitreenode(parentNode, 'Text', nodeNameStr, 'NodeData', nodeData);

                for k = 1:length(val)
                    nodeData_k.class = class(val{k});
                    nodeData_k.path = sprintf('%s{%d}', nodeData.path, k);
                    
                    
                    nodeText_k = sprintf('%s{%d} [%s]', name, k, class(val{k}));

                    uitreenode(childNode, 'Text', nodeText_k, 'NodeData', nodeData_k );
                end
            else
                nodeData.class = class(val);
                nodeData.text = nodeNameStr;
                nodeData.path = sprintf('%s.%s', parentNode.NodeData.path, name);
                childNode = uitreenode(parentNode, 'Text', nodeNameStr, 'NodeData', nodeData);
            end

            [isLeaf, names] = obj.leafTest(val);
            if ~isLeaf
                cellfun(@(x) obj.buildNode(childNode, val, x), names);
            end
        end

        function [isLeaf, names] = leafTest(~, s)
            try
                names = fieldnames(s);
                isLeaf = false;
            catch
                names = [];
                isLeaf = true;
            end
        end
        
        
        %%
        function obj = make_UIs(obj)
            pos = obj.container.Position; cWidth = pos(3); cHeight = pos(4);

            leftMargin = 10; rightMargin = 10; topMargin = 50; bottomMargin = 10;
            buttonWidth = 60; buttonHeight = 22; horizGap = 20; vertGap = 15; 

            obj.tree = uitree(obj.container, 'Position', [leftMargin, bottomMargin+buttonHeight+vertGap, cWidth-leftMargin-rightMargin, cHeight-bottomMargin-vertGap-topMargin], ...
                                         'SelectionChangedFcn', @(tree, event) obj.SelectionChanged(tree, event) );
            obj.ui.buttonDisp = uibutton(obj.container, 'Position', [leftMargin, bottomMargin, buttonWidth, buttonHeight], 'Text', 'Display', 'ButtonPushedFcn', @(btn, event) obj.BtnDispPushed(btn, event) );
            obj.ui.buttonHome = uibutton(obj.container, 'Position', [leftMargin+buttonWidth+horizGap, bottomMargin, buttonWidth, buttonHeight], 'Text', 'Root', 'ButtonPushedFcn', @(btn, event) obj.BtnHomePushed(btn, event) );
        end

        function BtnDispPushed(obj, ~, ~)
            obj.tree.Enable = false;
            obj.ui.buttonDisp.Enable = false;
            obj.ui.buttonHome.Enable = false;
            waitfor(obj.ui.buttonHome, 'Enable', false);
            
            path2node = obj.tree.SelectedNodes.NodeData.path;
            
            s = eval(path2node);
            obj.buildTree(s, path2node, path2node);
            
            obj.tree.Enable = true;
            obj.ui.buttonDisp.Enable = true;
            obj.ui.buttonHome.Enable = true;
        end

        function BtnHomePushed(obj, ~, ~)
            obj.tree.Enable = false;
            obj.ui.buttonDisp.Enable = false;
            obj.ui.buttonHome.Enable = false;
            waitfor(obj.ui.buttonHome, 'Enable', false);
            
            obj.buildTree(obj.rootData, 'Root', 'obj.rootData');
            
            obj.tree.Enable = true;
            obj.ui.buttonDisp.Enable = true;
        end

        function SelectionChanged(obj, ~, event)
            obj.selectedNodePath = event.SelectedNodes.NodeData.path;
            notify(obj, 'SelectedNodeChanged');
            if strcmp('struct', event.SelectedNodes.NodeData.class) || ~isempty(event.SelectedNodes.Children)
                obj.ui.buttonDisp.Enable = true;
            else
                obj.ui.buttonDisp.Enable = false;
            end
        end
    end
end

